use std::{collections::BTreeMap, panic};

use darling::{FromDeriveInput, FromMeta};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as T2;
use quote::{format_ident, quote};
use syn::{DeriveInput, Ident, parse_macro_input};

use crate::periph::{
    rcc::{self, RccAxB},
    steal_periph,
};

#[derive(Debug, FromMeta)]
pub enum Mode {
    #[darling(rename = "IN")]
    Input,

    #[darling(rename = "OUT")]
    Output,

    #[darling(rename = "AF")]
    Alternate,

    #[darling(rename = "AIN")]
    Analog,
}

#[derive(Debug, FromMeta)]
pub enum Ospeed {
    #[darling(rename = "OS2")]
    Speed2,

    #[darling(rename = "OS25")]
    Speed25,

    #[darling(rename = "OS50")]
    Speed50,

    #[darling(rename = "OS100")]
    Speed100,
}

impl Default for Ospeed {
    fn default() -> Self {
        Ospeed::Speed100
    }
}

#[derive(Debug, FromMeta)]
pub enum Pupd {
    #[darling(rename = "FLOAT")]
    Float,

    #[darling(rename = "UP")]
    Up,

    #[darling(rename = "DOWN")]
    Down,
}

impl Default for Pupd {
    fn default() -> Self {
        Pupd::Float
    }
}

#[derive(Debug, FromMeta)]
pub enum Otype {
    #[darling(rename = "PP")]
    PP,

    #[darling(rename = "OD")]
    OD,
}

#[derive(Debug, FromMeta)]
pub enum Alternate {
    #[darling(rename = "AF0")]
    AF0,

    #[darling(rename = "SYS")]
    SYS,

    #[darling(rename = "TIM1")]
    TIM1,

    #[darling(rename = "TIM2")]
    TIM2,

    #[darling(rename = "TIM3")]
    TIM3,

    #[darling(rename = "TIM4")]
    TIM4,

    #[darling(rename = "TIM5")]
    TIM5,

    #[darling(rename = "TIM8")]
    TIM8,

    #[darling(rename = "TIM9")]
    TIM9,

    #[darling(rename = "TIM10")]
    TIM10,

    #[darling(rename = "TIM11")]
    TIM11,

    #[darling(rename = "I2C1")]
    I2C1,

    #[darling(rename = "I2C2")]
    I2C2,

    #[darling(rename = "I2C3")]
    I2C3,

    #[darling(rename = "SPI1")]
    SPI1,

    #[darling(rename = "SPI2")]
    SPI2,

    #[darling(rename = "I2S2")]
    I2S2,

    #[darling(rename = "I2S2Ext")]
    I2S2Ext,

    #[darling(rename = "SPI3")]
    SPI3,

    #[darling(rename = "I2SExt")]
    I2SExt,

    #[darling(rename = "I2S3")]
    I2S3,

    #[darling(rename = "USART1")]
    USART1,

    #[darling(rename = "USART2")]
    USART2,

    #[darling(rename = "USART3")]
    USART3,

    #[darling(rename = "I2S3Ext")]
    I2S3Ext,

    #[darling(rename = "UART4")]
    UART4,

    #[darling(rename = "UART5")]
    UART5,

    #[darling(rename = "USART6")]
    USART6,

    #[darling(rename = "CAN1")]
    CAN1,

    #[darling(rename = "CAN2")]
    CAN2,

    #[darling(rename = "TIM12")]
    TIM12,

    #[darling(rename = "TIM13")]
    TIM13,

    #[darling(rename = "TIM14")]
    TIM14,

    #[darling(rename = "OTGFS")]
    OTGFS,

    #[darling(rename = "OTGHS")]
    OTGHS,

    #[darling(rename = "ETH")]
    ETH,

    #[darling(rename = "FSMC")]
    FSMC,

    #[darling(rename = "SDIO")]
    SDIO,

    #[darling(rename = "OTGFS2")]
    OTGFS2,

    #[darling(rename = "DCMI")]
    DCMI,

    #[darling(rename = "AF1")]
    AF1,

    #[darling(rename = "AF2")]
    AF2,

    #[darling(rename = "AF3")]
    AF3,

    #[darling(rename = "AF4")]
    AF4,

    #[darling(rename = "AF5")]
    AF5,

    #[darling(rename = "AF6")]
    AF6,

    #[darling(rename = "AF7")]
    AF7,

    #[darling(rename = "AF8")]
    AF8,

    #[darling(rename = "AF9")]
    AF9,

    #[darling(rename = "AF10")]
    AF10,

    #[darling(rename = "AF11")]
    AF11,

    #[darling(rename = "AF12")]
    AF12,

    #[darling(rename = "AF13")]
    AF13,

    #[darling(rename = "AF14")]
    AF14,

    #[darling(rename = "AF15")]
    AF15,
}

#[derive(Debug, FromMeta)]
pub struct Attr {
    pub name: String,

    pub io: String,

    pub mode: Mode,

    #[darling(default)]
    pub ospeed: Ospeed,

    #[darling(default)]
    pub pupd: Pupd,

    #[darling(default)]
    pub otype: Option<Otype>,

    #[darling(default)]
    pub af: Option<Alternate>,

    #[darling(default)]
    pub level: Option<bool>,

    #[darling(default)]
    pub set: bool,

    #[darling(default)]
    pub reset: bool,

    #[darling(default)]
    pub toggle: bool,

    #[darling(default)]
    pub read: bool,

    #[darling(default)]
    pub exti_up: bool,

    #[darling(default)]
    pub exti_down: bool,
}

impl Attr {
    fn gpio_id(&self) -> String {
        let n: &str = &self.io;
        let name = &n[1..2];
        name.to_lowercase()
    }

    fn pin(&self) -> u8 {
        let n = &self.io;
        (&n[2..]).parse().unwrap()
    }

    fn create_is_exti_pend(&self) -> T2 {
        let steal_exti = steal_periph("EXTI");
        let name = format_ident!("{}_is_exti_pend", self.name);
        let pr = format_ident!("pr{}", self.pin());
        quote! {
            #[inline(always)]
            pub fn #name() -> bool {
                let exti = #steal_exti;
                exti.pr().read().#pr().is_pending()
            }
        }
    }

    fn create_clear_exti_pend(&self) -> T2 {
        let steal_exti = steal_periph("EXTI");
        let name = format_ident!("{}_clear_exti_pend", self.name);
        let pr = format_ident!("pr{}", self.pin());
        quote! {
            #[inline(always)]
            pub fn #name() {
                let exti = #steal_exti;
                exti.pr().write(|w| w.#pr().clear());
            }
        }
    }

    fn build_exti(&self) -> T2 {
        let steal_syscfg = steal_periph("SYSCFG");
        let pin = self.pin();
        let exticr = match pin {
            0..=3 => format_ident!("exticr1"),
            4..=7 => format_ident!("exticr2"),
            8..=11 => format_ident!("exticr3"),
            12..=15 => format_ident!("exticr4"),
            _ => panic!("invalid pin"),
        };
        let exti = format_ident!("exti{}", pin);
        let pxx = format_ident!("p{}", self.gpio_id());
        let steal_exti = steal_periph("EXTI");
        let mr = format_ident!("mr{}", pin);
        let tr = format_ident!("tr{}", pin);

        let mut expand = quote! {
            let syscfg = #steal_syscfg;
            syscfg.#exticr().modify(|_, w| w.#exti().#pxx());
            let exti = #steal_exti;
            exti.imr().modify(|_, w| w.#mr().unmasked());
        };

        if self.exti_up {
            expand.extend(quote! {
                let exti = #steal_exti;
                exti.rtsr().modify(|_, w| w.#tr().enabled());
            });
        }
        if self.exti_down {
            expand.extend(quote! {
                let exti = #steal_exti;
                exti.ftsr().modify(|_, w| w.#tr().enabled());
            });
        }

        expand
    }

    fn create_set(&self) -> T2 {
        let name = format_ident!("{}_set", self.name);
        let steal_name = format!("GPIO{}", self.gpio_id().to_uppercase());
        let steal_name = steal_periph(&steal_name);
        let bs = format_ident!("bs{}", self.pin());

        quote! {
            #[inline(always)]
            pub fn #name() {
                let gpio = #steal_name;
                gpio.bsrr().write(|w| w.#bs().set_());
            }
        }
    }

    fn create_reset(&self) -> T2 {
        let name = format_ident!("{}_reset", self.name);
        let steal_name = format!("GPIO{}", self.gpio_id().to_uppercase());
        let steal_name = steal_periph(&steal_name);
        let br = format_ident!("br{}", self.pin());

        quote! {
            #[inline(always)]
            pub fn #name() {
                let gpio = #steal_name;
                gpio.bsrr().write(|w| w.#br().reset());
            }
        }
    }

    fn create_toggle(&self) -> T2 {
        let name = format_ident!("{}_toggle", self.name);
        let steal_name = format!("GPIO{}", self.gpio_id().to_uppercase());
        let steal_name = steal_periph(&steal_name);
        let odr = format_ident!("odr{}", self.pin());

        quote! {
            #[inline(always)]
            pub fn #name() {
                let gpio = #steal_name;
                gpio.odr().modify(|r, w| w.#odr().bit(!r.#odr().bit()));
            }
        }
    }

    fn create_read(&self) -> T2 {
        let name = format_ident!("{}_read", self.name);
        let steal_name = format!("GPIO{}", self.gpio_id().to_uppercase());
        let steal_name = steal_periph(&steal_name);
        let idr = format_ident!("idr{}", self.pin());

        quote! {
            #[inline(always)]
            pub fn #name() -> bool {
                let gpio = #steal_name;
                gpio.idr().read().#idr().is_high()
            }
        }
    }

    fn build_mode(&self) -> T2 {
        let pin = self.pin();
        let mode = format_ident!("moder{}", pin);
        match self.mode {
            Mode::Alternate => quote! {
                #mode().alternate()
            },
            Mode::Analog => quote! {
                #mode().analog()
            },
            Mode::Input => quote! {
                #mode().input()
            },
            Mode::Output => quote! {
                #mode().output()
            },
        }
    }

    fn build_ospeed(&self) -> Option<T2> {
        match self.mode {
            Mode::Alternate | Mode::Output => {
                let ospeed = format_ident!("ospeedr{}", self.pin());
                let value = match self.ospeed {
                    Ospeed::Speed2 => format_ident!("low_speed"),
                    Ospeed::Speed25 => format_ident!("medium_speed"),
                    Ospeed::Speed50 => format_ident!("high_speed"),
                    Ospeed::Speed100 => format_ident!("very_high_speed"),
                };
                Some(quote! {
                    #ospeed().#value()
                })
            }
            _ => None,
        }
    }

    fn build_otype(&self) -> Option<T2> {
        match self.mode {
            Mode::Alternate | Mode::Output => {
                let ot = format_ident!("ot{}", self.pin());
                let value = match self.otype {
                    None => panic!("no otype"),
                    Some(Otype::PP) => format_ident!("push_pull"),
                    Some(Otype::OD) => format_ident!("open_drain"),
                };
                Some(quote! {
                    #ot().#value()
                })
            }
            _ => None,
        }
    }

    fn build_pupd(&self) -> T2 {
        let pupd = format_ident!("pupdr{}", self.pin());
        match self.pupd {
            Pupd::Float => quote! {
                #pupd().floating()
            },

            Pupd::Down => quote! {
                #pupd().pull_down()
            },

            Pupd::Up => quote! {
                #pupd().pull_up()
            },
        }
    }

    fn build_af(&self, afl: &mut Vec<T2>, afh: &mut Vec<T2>) {
        match self.mode {
            Mode::Alternate => {}
            _ => return,
        };

        let pin = self.pin();

        let af = if pin < 8 {
            format_ident!("afrl{}", pin)
        } else {
            format_ident!("afrh{}", pin)
        };
        let afv = if pin < 8 { afl } else { afh };

        match self.af {
            Some(Alternate::AF0 | Alternate::SYS) => {
                afv.push(quote! {
                    #af().af0()
                });
            }
            Some(Alternate::AF1 | Alternate::TIM1 | Alternate::TIM2) => {
                afv.push(quote! {
                    #af().af1()
                });
            }
            Some(Alternate::AF2 | Alternate::TIM3 | Alternate::TIM4 | Alternate::TIM5) => {
                afv.push(quote! {
                    #af().af2()
                });
            }
            Some(
                Alternate::AF3
                | Alternate::TIM8
                | Alternate::TIM9
                | Alternate::TIM10
                | Alternate::TIM11,
            ) => {
                afv.push(quote! {
                    #af().af3()
                });
            }
            Some(Alternate::AF4 | Alternate::I2C1 | Alternate::I2C2 | Alternate::I2C3) => {
                afv.push(quote! {
                    #af().af4()
                });
            }
            Some(
                Alternate::AF5
                | Alternate::SPI1
                | Alternate::SPI2
                | Alternate::I2S2
                | Alternate::I2S2Ext,
            ) => {
                afv.push(quote! {
                    #af().af5()
                });
            }
            Some(Alternate::AF6 | Alternate::SPI3 | Alternate::I2SExt | Alternate::I2S3) => {
                afv.push(quote! {
                    #af().af6()
                });
            }
            Some(
                Alternate::AF7
                | Alternate::USART1
                | Alternate::USART2
                | Alternate::USART3
                | Alternate::I2S3Ext,
            ) => {
                afv.push(quote! {
                    #af().af7()
                });
            }
            Some(Alternate::AF8 | Alternate::UART4 | Alternate::UART5 | Alternate::USART6) => {
                afv.push(quote! {
                    #af().af8()
                });
            }
            Some(
                Alternate::AF9
                | Alternate::CAN1
                | Alternate::CAN2
                | Alternate::TIM12
                | Alternate::TIM13
                | Alternate::TIM14,
            ) => {
                afv.push(quote! {
                    #af().af9()
                });
            }
            Some(Alternate::AF10 | Alternate::OTGFS | Alternate::OTGHS) => {
                afv.push(quote! {
                    #af().af10()
                });
            }
            Some(Alternate::AF11 | Alternate::ETH) => {
                afv.push(quote! {
                    #af().af11()
                });
            }
            Some(Alternate::AF12 | Alternate::FSMC | Alternate::SDIO | Alternate::OTGFS2) => {
                afv.push(quote! {
                    #af().af12()
                });
            }
            Some(Alternate::AF13 | Alternate::DCMI) => {
                afv.push(quote! {
                    #af().af13()
                });
            }
            Some(Alternate::AF14) => {
                afv.push(quote! {
                    #af().af14()
                });
            }
            Some(Alternate::AF15) => {
                afv.push(quote! {
                    #af().af15()
                });
            }
            None => {}
        }
    }

    fn build_bsrr(&self) -> Option<T2> {
        let pin = self.pin();
        let level = match self.level {
            Some(l) => l,
            None => return None,
        };
        match self.mode {
            Mode::Output => {
                let bsrr = if level {
                    let bs = format_ident!("bs{}", pin);
                    quote! {
                        #bs().set_()
                    }
                } else {
                    let br = format_ident!("br{}", pin);
                    quote! {
                        #br().reset()
                    }
                };
                Some(bsrr)
            }
            _ => None,
        }
    }
}

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(gpio))]
pub struct Config {
    #[darling(multiple)]
    pub io: Vec<Attr>,
    pub init: String,
}

impl Config {
    pub fn build(&self) -> T2 {
        let mut map: BTreeMap<String, Vec<&Attr>> = BTreeMap::new();
        let mut utils = Vec::new();
        let mut exti_flag = false;
        let mut exti_init = quote! {};
        for attr in self.io.iter() {
            let gpio = attr.gpio_id();

            let ret = map.get_mut(&gpio);
            if let Some(vec) = ret {
                vec.push(attr);
            } else {
                let mut vec = Vec::new();
                vec.push(attr);
                map.insert(gpio, vec);
            }

            if attr.set {
                utils.push(attr.create_set());
            }
            if attr.reset {
                utils.push(attr.create_reset());
            }
            if attr.toggle {
                utils.push(attr.create_toggle());
            }
            if attr.read {
                utils.push(attr.create_read());
            }

            if attr.exti_up || attr.exti_down {
                exti_flag = true;

                exti_init.extend(attr.build_exti());
                utils.push(attr.create_is_exti_pend());
                utils.push(attr.create_clear_exti_pend());
            }
        }

        let fields: Vec<Ident> = map.keys().map(|id| format_ident!("gpio{}en", id)).collect();
        let rcc_init = rcc::rcc_enable_all(rcc::RccAxB::AHB1, &fields, false);
        let rcc_syscfg = if exti_flag {
            rcc::rcc_enable(RccAxB::APB2, &format_ident!("syscfgen"), false)
        } else {
            quote! {}
        };

        let mut group_expand = quote! {};
        for (key, value) in map.iter() {
            let r = self.build_group(&key, &value);
            group_expand.extend(r);
        }

        let init_fn = format_ident!("{}", self.init);
        let init_gpio_fn = format_ident!("{}_no_rcc", self.init);
        quote! {

            #[inline(always)]
            pub fn #init_gpio_fn() {
                #group_expand
            }

            #[inline(always)]
            pub fn #init_fn() {
                #rcc_init
                #rcc_syscfg
                #group_expand
                #exti_init
            }

            #(#utils)*
        }
    }

    fn build_group(&self, gpio_id: &str, attrs: &[&Attr]) -> T2 {
        let mut modes = Vec::new();
        let mut ospeeds = Vec::new();
        let mut ots = Vec::new();
        let mut pupds = Vec::new();
        let mut afl = Vec::new();
        let mut afh = Vec::new();
        let mut bsrr = Vec::new();

        for attr in attrs {
            modes.push(attr.build_mode());
            if let Some(ospeed) = attr.build_ospeed() {
                ospeeds.push(ospeed);
            }
            if let Some(ot) = attr.build_otype() {
                ots.push(ot);
            }
            pupds.push(attr.build_pupd());

            attr.build_af(&mut afl, &mut afh);

            if let Some(bs) = attr.build_bsrr() {
                bsrr.push(bs);
            }
        }
        let gpio_name = format!("GPIO{}", gpio_id.to_uppercase());
        let steal_gpio = steal_periph(&gpio_name);
        let mut expand = quote! {};

        expand.extend(quote! {
            gpio.moder().modify(|_, w| w #(.#modes)*);
        });

        if !ospeeds.is_empty() {
            expand.extend(quote! {
                gpio.ospeedr().modify(|_, w| w #(.#ospeeds)*);
            });
        }

        if !ots.is_empty() {
            expand.extend(quote! {
                gpio.otyper().modify(|_, w| w #(.#ots)*);
            });
        }

        expand.extend(quote! {
            gpio.pupdr().modify(|_, w| w #(.#pupds)*);
        });

        if !afl.is_empty() {
            expand.extend(quote! {
                gpio.afrl().modify(|_, w| w #(.#afl)*);
            });
        }
        if !afh.is_empty() {
            expand.extend(quote! {
                gpio.afrh().modify(|_, w| w #(.#afh)*);
            });
        }
        if !bsrr.is_empty() {
            expand.extend(quote! {
                gpio.bsrr().write(|w| w #(.#bsrr)*);
            });
        }

        quote! {
            let gpio = #steal_gpio;
            #expand
        }
    }
}

pub fn build(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let config = Config::from_derive_input(&input).unwrap();
    let build = config.build();
    let expand = quote! {
        impl #name {
            #build
        }
    };
    expand.into()
}
